package com.yungu.swift.autoconfig.redis;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: cuixiuyin
 * @Date: 2019/5/29
 */

@Getter
@Slf4j
public class RedisStringCacheService {

    private StringRedisTemplate stringRedisTemplate;

    private ValueOperations<String, String> valueOps;

    private HashOperations<String, String, String> hashOps;

    private ListOperations<String, String> listOps;

    private SetOperations<String, String> setOps;

    private ZSetOperations<String, String> zsetOps;

    private String keyPrefix;

    public RedisStringCacheService(String keyPrefix, StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.valueOps = stringRedisTemplate.opsForValue();
        this.hashOps = stringRedisTemplate.opsForHash();
        this.listOps = stringRedisTemplate.opsForList();
        this.setOps = stringRedisTemplate.opsForSet();
        this.zsetOps = stringRedisTemplate.opsForZSet();

        this.keyPrefix = keyPrefix.endsWith("_") ? keyPrefix : keyPrefix + "_";
    }

    private String genKey(String key) {
        return keyPrefix + key;
    }

    public boolean exists(String key) {
        return stringRedisTemplate.hasKey(genKey(key));
    }

    public void invalidKey(String key) {
        stringRedisTemplate.delete(genKey(key));
    }

    /**
     * 新增方法，方法名尽量与命令保持一直，降低 php 等其他语言切换的理解成本。
     */

    /*
     * 简单操作=================================================
     */

    /**
     * 设置值。
     *
     * @param key   键。
     * @param value 字符串形式的值。
     */
    public void set(String key, String value) {
        valueOps.set(genKey(key), value);
    }

    /**
     * 设置值。
     *
     * @param key    键。
     * @param value  字符串形式的值。
     * @param expire 失效时间(分)。
     */
    public void set(String key, String value, int expire) {
        valueOps.set(genKey(key), value, expire, TimeUnit.MINUTES);
    }

    /**
     * 设置值。
     *
     * @param key      键。
     * @param value    字符串形式的值。
     * @param expire   失效时间。
     * @param timeUnit 时间类型
     */
    public void set(String key, String value, int expire, TimeUnit timeUnit) {
        valueOps.set(genKey(key), value, expire, timeUnit);
    }

    /**
     *
     * @param key
     * @param value
     * @param expire
     * @param timeUnit
     * @param isFixKey 是否加前缀
     */
    public void set(String key, String value, int expire, TimeUnit timeUnit,boolean isFixKey) {
        if(isFixKey){
            valueOps.set(genKey(key), value, expire, timeUnit);
        }else{
            valueOps.set(key, value, expire, timeUnit);
        }
    }

    /**
     * 如果key不存在，设置值。
     *
     * @param key   键。
     * @param value 字符串形式的值。
     * @return 设置成功返回true；未设置返回false；
     */
    public boolean setnx(String key, String value) {
        return valueOps.setIfAbsent(genKey(key), value);
    }

    /**
     * 设置新的 value ，并返回旧的 value
     *
     * @param key
     * @param value
     * @return
     */
    public String getSet(String key, String value) {
        return valueOps.getAndSet(key, value);
    }


    /**
     * 批量设置值。
     *
     * @param map 包含键值对的map。
     */
    public void mset(Map<String, String> map) {
        Map<String, String> genMap = new HashMap<>();
        map.forEach((k, v) -> genMap.put(genKey(k), v));
        valueOps.multiSet(genMap);
    }

    /**
     * 通过键获取字符串形式的值。
     *
     * @param key 键。
     * @return key对应的值。
     */
    public String get(String key) {
        return valueOps.get(genKey(key));
    }

    /**
     *
     * @param key
     * @param isFixKey 是否加前缀
     * @return
     */
    public String get(String key,boolean isFixKey) {
        if(isFixKey){
            return valueOps.get(genKey(key));
        }else{
            return valueOps.get(key);
        }
    }

    /**
     * 批量通过键获取值的集合。
     *
     * @param keys 键的集合。
     * @return 对应的值的集合。
     */
    public List<String> mget(List<String> keys) {
        return valueOps.multiGet(keys.stream().map(this::genKey).collect(Collectors.toList()));
    }

    /**
     * 批量通过键获取值的集合。
     *
     * @param keys 键数组。
     * @return 对应的值的集合。
     */
    public List<String> mget(String... keys) {
        List<String> keyList = Arrays.asList(keys);
        return valueOps.multiGet(keyList.stream().map(this::genKey).collect(Collectors.toList()));
    }

    /**
     * 通过键删除对应的值。
     *
     * @param key 键。
     */
    public void del(String key) {
        stringRedisTemplate.delete(genKey(key));
    }

    /**
     *
     * @param key
     * @param isFixKey 是否加前缀
     */
    public void del(String key,boolean isFixKey) {
        if(isFixKey){
            stringRedisTemplate.delete(genKey(key));
        }else{
            stringRedisTemplate.delete(key);
        }
    }

    /**
     * 递增指定键对应的数值。
     * <p>如果不存在key对应的值，那么会先将key的值设置为0，
     * 然后执行incr操作，返回1。
     *
     * @param key 键。
     * @return 递增后key对应的值。
     */
    public Long incr(String key) {

        return valueOps.increment(genKey(key), 1);
    }

    /**
     * 将指定键对应的数值增加给定的增量。
     * <p>如果不存在key对应的值，那么会先将key的值设置为0，
     * 然后执行incrBy操作，返回increment。
     *
     * @param key       键。
     * @param increment 增量值。
     * @return 递增后key对应的值。
     */
    public long incrBy(String key, long increment) {
        return valueOps.increment(genKey(key), increment);
    }

    /**
     * 给指定的键设置超时时间。
     *
     * @param key     键。
     * @param minutes 超时时间(单位:分)。
     * @return 如果成功设置超时，返回true；
     * 如果key不存在或者未成功设置超时，返回false。
     */
    public boolean expire(String key, int minutes) {
        return stringRedisTemplate.expire(genKey(key), minutes, TimeUnit.MINUTES);
    }

    /**
     * 给指定的键设置超时时间。
     *
     * @param key     键。
     * @param time 超时时间。
     *@param timeUnit 自定义单位。
     * @return 如果成功设置超时，返回true；
     * 如果key不存在或者未成功设置超时，返回false。
     */
    public boolean expire(String key, int time,TimeUnit timeUnit) {
        return stringRedisTemplate.expire(genKey(key), time, timeUnit);
    }


    /*
     * 哈希表相关操作====================================
     */

    /**
     * 为键对应的哈希表设置一个字段及对应的值。
     *
     * @param key   哈希表在redis中的key。
     * @param field 哈希表中的字段。
     * @param value field对应的字符串形式的值。
     * @return 如果field是哈希表中的一个新字段，返回1；
     * 如果field已经在哈希表中，值被更新，返回0；
     */
    public void hset(String key, String field, String value) {
        hashOps.put(genKey(key), field, value);
    }

    /**
     * 为键对应的哈希表设置多个字段及对应的值。
     *
     * @param key 哈希表在redis中的key。
     * @param map 多个字段及对应值组成的哈希表。
     */
    public void hmset(String key, Map<String, String> map) {
        hashOps.putAll(genKey(key), map);
    }

    /**
     * 从键对应的哈希表中获取给定字段对应的值。
     *
     * @param key   哈希表在redis中的key。
     * @param field 哈希表中的字段。
     * @return 哈希表中field对应的字符串形式的值。
     */
    public String hget(String key, String field) {
        return hashOps.get(genKey(key), field);
    }

    /**
     * 从键对应的哈希表中获取给定一批字段对应的值。
     *
     * @param key    哈希表在redis中的key。
     * @param fields 哈希表中的一批字段。
     * @return 哈希表中fields对应的字符串形式的值的集合。
     */
    public List<String> hmget(String key, String... fields) {
        return hashOps.multiGet(genKey(key), Arrays.asList(fields));
    }

    /**
     * 获取键对应的哈希表。
     *
     * @param key 哈希表在redis中的key。
     * @return key对应的哈希表。
     */
    public Map<String, String> hgetAll(String key) {
        return hashOps.entries(genKey(key));
    }

    /**
     * 从键对应的哈希表中删除给定字段对应的值。
     *
     * @param key    键。
     * @param fields 哈希表中的字段。
     * @return 返回从哈希表中删除字段的数量。
     */
    public long hdel(String key, String... fields) {
        return hashOps.delete(genKey(key), fields);
    }

    /**
     * 判断键对应的哈希表中是否存在给定字段及对应的值。
     *
     * @param key   键。
     * @param field 哈希表中的字段。
     * @return 如果存在，返回true；不存在返回false；
     */
    public boolean hexists(String key, String field) {
        return hashOps.hasKey(genKey(key), field);
    }

    /**
     * 获取给定键对应的哈希表中所有字段。
     *
     * @param key 键。
     * @return key对应的哈希表的所有字段的集合。
     */
    public Set<String> hkeys(String key) {
        return hashOps.keys(genKey(key));
    }

    /**
     * 获取给定键对应的哈希表中的字段数量。
     *
     * @param key 键。
     * @return key对应的哈希表的字段数量。
     */
    public long hlen(String key) {
        return hashOps.size(genKey(key));
    }

    /*
     * 列表相关操作=================================
     */

    /**
     * 获取给定键对应的列表的指定下标的字符串形式的值。
     *
     * @param key   键。
     * @param index 下标。
     * @return 如果指定下标存在值，返回该值；否则返回null；
     */
    public String lindex(String key, long index) {
        return listOps.index(genKey(key), index);
    }

    /**
     * 在给定键对应的列表头部插入一个或多个字符串形式的值。
     *
     * @param key    键。
     * @param values 要插入链表的值。
     * @return 返回链表的长度。
     */
    public long lpush(String key, String... values) {
        return listOps.leftPushAll(genKey(key), values);
    }

    /**
     * 获取给定键对应的列表头部的元素值。
     *
     * @param key 键。
     * @return 链表头部的字符串形式的值。
     */
    public String lpop(String key) {
        return listOps.leftPop(genKey(key));
    }

    /**
     * 获取给定键对应列表中的第一个字符串形式的元素，或者阻塞直到有可用元素或者超时。
     *
     * @param key     键。
     * @param timeout 超时时间(分)。
     * @return 列表中的第一个元素；如果超时返回null。
     */
    public String blpop(String key, int timeout) {
        return listOps.leftPop(genKey(key), timeout, TimeUnit.MINUTES);
    }

    /**
     * 在给定键对应的列表尾部插入一个或多个字符串形式的值。
     *
     * @param key    键。
     * @param values 要插入链表的值。
     * @return 返回链表的长度。
     */
    public long rpush(String key, String... values) {
        return listOps.rightPushAll(genKey(key), values);
    }

    /**
     * 获取给定键对应的列表尾部的元素值。
     *
     * @param key 键。
     * @return 链表尾部的字符串形式的值。
     */
    public String rpop(String key) {
        return listOps.rightPop(genKey(key));
    }

    /**
     * 获取给定键对应列表中的最后一个字符串形式的元素，或者阻塞直到有可用元素或者超时。
     *
     * @param key     键。
     * @param timeout 超时时间(分)。
     * @return 列表中的第一个元素；如果超时返回null。
     */
    public String brpop(String key, int timeout) {
        return listOps.rightPop(genKey(key), timeout, TimeUnit.MINUTES);
    }

    /**
     * 获取给定键对应的列表长度。
     *
     * @param key 键。
     * @return 如果存在列表，返回列表长度；否则返回0；
     */
    public long llen(String key) {
        return listOps.size(genKey(key));
    }

    /**
     * 获取给定键对应的列表的指定范围的字符串形式的元素。
     *
     * @param key   键。
     * @param start 起始范围。
     * @param end   结束返回。
     * @return 指定范围内的元素集合。
     */
    public List<String> lrange(String key, long start, long end) {
        return listOps.range(genKey(key), start, end);
    }

    /*
     * 集合相关操作=================================
     */

    /**
     * 为给定键对应的集合中添加一个或多个字符串形式的元素。
     *
     * @param key    键。
     * @param values 要添加的元素。
     * @return 成功添加的元素数量。
     */
    public long sadd(String key, String... values) {
        return setOps.add(genKey(key), values);
    }

    /**
     * 删除给定键对应的集合元素。
     *
     * @param key    键。
     * @param values 要删除的元素。
     * @return 成功删除的元素数量。
     */
    public long srem(String key, String... values) {
        return setOps.remove(genKey(key), values);
    }

    /**
     * 获取给定键对应的集合元素。
     *
     * @param key 键。
     * @return 集合中所有元素
     */
    public Set<String> smembers(String key) {
        return setOps.members(genKey(key));
    }

    /**
     * 判断给定元素是否在给定键对应的集合中。
     *
     * @param key   键。
     * @param value 元素。
     * @return 如果value是key对应集合中的元素，返回true；否则返回false。
     */
    public boolean sismember(String key, String value) {
        return setOps.isMember(genKey(key), value);
    }

    /*
     * 有序集合相关操作=================================
     */

    /**
     * 给指定键对应的有序集合添加元素。
     *
     * @param key   键。
     * @param value 字符串形式的元素。
     * @param score 元素的分数。
     * @return 如果集合中不存在value，成功插入集合，返回true。
     * 如果集合中已存在value，更新集合元素，返回false。
     */
    public boolean zadd(String key, String value, double score) {
        return zsetOps.add(genKey(key), value, score);
    }

    /**
     * 给指定键对应的有序集合添加元素。
     *
     * @param key 键。
     * @param map 要插入的元素和分数对。
     * @return 成功插入数量。
     */
    public long zadd(String key, Map<String, Double> map) {
        if (map == null || map.size() == 0) {
            return 0;
        }
        Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>(map.size());
        for (String k : map.keySet()) {
            DefaultTypedTuple<String> tuple = new DefaultTypedTuple<>(k, map.get(k));
            tuples.add(tuple);
        }
        return zsetOps.add(genKey(key), tuples);
    }

    /**
     * 删除指定键对应的有序集合中的一批元素。
     *
     * @param key    键。
     * @param values 要删除的字符串形式的元素。
     * @return 从集合中删除的元素数量。
     */
    public long zrem(String key, String... values) {
        return zsetOps.remove(genKey(key), values);
    }

    /**
     * 获取指定键对应的有序集合中的元素个数。
     *
     * @param key 键。
     * @return 元素个数。
     */
    public long zcard(String key) {
        return zsetOps.zCard(genKey(key));
    }

    /**
     * 从指定键对应的有序集合中获取给定下标范围的字符串形式的元素。
     * <p>元素顺序按分数由小到大。
     *
     * @param key   键。
     * @param start 起始下标。(下标从0开始)
     * @param end   结束下标。
     * @return 指定下标范围内的元素集合。
     */
    public Set<String> zrange(String key, long start, long end) {
        return zsetOps.range(genKey(key), start, end);
    }

    public long zcount(String key, double min, double max) {
        return zsetOps.count(genKey(key), min, max);
    }

    public void watch(String key) {
        stringRedisTemplate.watch(genKey(key));
    }

    public List<Object> exec() {
        return stringRedisTemplate.exec();
    }

    public void multi() {
        stringRedisTemplate.multi();
    }

    public void setEnableTransactionSupport(boolean enable) {
        stringRedisTemplate.setEnableTransactionSupport(enable);
    }

    public boolean rename(String oldKey, String newKey) {
        return stringRedisTemplate.renameIfAbsent(genKey(oldKey), genKey(newKey));
    }
}
